package com.dcivision.lucene;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexModifier;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import com.dcivision.framework.ApplicationException;
import com.dcivision.framework.ErrorConstant;
import com.dcivision.framework.SystemParameterConstant;
import com.dcivision.framework.SystemParameterFactory;
import com.dcivision.framework.Utility;

/**
 * An factory class to set index instance.
 * All method and property is static or sigleton.
 * @author Administrator
 *
 */
class IndexFactory { 
  
  private static String indexPath;
  
  private static Directory directory;
  
  private static Analyzer analyzer;
  
  private static IndexModifier modifier;
  
  private static List threadList = new ArrayList();
  
  private static long commitInterval = 300000; //30S
  
  private static IndexCommitTask commitTask = null;
  
  private static long optimizeInterval = 12 * 3600 * 1000; //12H
  
  private static IndexOptimizeTask optimizeTask = null;
  
  /**
   * init class static properties.
   */
  static {
    try {
      String analyzerName = SystemParameterFactory.getSystemParameter(SystemParameterConstant.INDEX_DEFAULT_ANALYZER);
      String indexBasePath = SystemParameterFactory.getSystemParameter(SystemParameterConstant.INDEX_BASE_PATH);
      if (Utility.isEmpty(analyzerName)) {
        analyzerName = "org.apache.lucene.analysis.cjk.CJKAnalyzer";
      }
      if (Utility.isEmpty(indexBasePath)) {
        indexBasePath = "D:/INDEXTEST/file/Index";
      }
      File file = new File(indexBasePath);
      if (!file.exists()) {
        file.mkdirs();
      }
      
      indexPath = indexBasePath;
      directory = FSDirectory.getDirectory(file, false);
      analyzer = (Analyzer)Class.forName(analyzerName).newInstance();
    
      // check if index is locked, probably from an unclean repository, remove lock.
      if (IndexReader.isLocked(directory)) {
        IndexReader.unlock(directory);
      }
      // commit thread run;
      commitTask = new IndexCommitTask(commitInterval);
      optimizeTask = new IndexOptimizeTask(optimizeInterval);
      commitTask.start();
      optimizeTask.start();
    }catch (ClassNotFoundException e) {
      throw new RuntimeException("cannot load FSDirectory class: " + e.toString(), e);
    } catch (Exception ex) {
      throw new RuntimeException("IndexModifierFactory init failure: " + ex.toString(), ex);
    }
  }
  
  static IndexModifier getIndexModifier() throws ApplicationException {
    return getIndexModifier(true);
  }
  
  /**
   * Get segleton instance to modify lucene index, include insert and delete operation.
   * @return
   * @throws IOException
   */
  static IndexModifier getIndexModifier(boolean lockThread) throws ApplicationException {
    // regedit update index thread
    if (lockThread && !threadList.contains(Thread.currentThread())) {
      synchronized (threadList) {
        if (!threadList.contains(Thread.currentThread())) {
          threadList.add(Thread.currentThread());
        }
      }
    }
    // 
    if (modifier == null) {
      try {
        synchronized (IndexFactory.class) {
          if (modifier==null) {
            if (!IndexReader.indexExists(directory)) {
              modifier = new IndexModifier(directory, analyzer, true);
            }else {
              modifier = new IndexModifier(directory, analyzer, false);
            }
            modifier.setMergeFactor(200);
            modifier.setUseCompoundFile(true);
            modifier.setMaxFieldLength(100000000);
          }
        }
      }catch (Exception ex) {
        throw new ApplicationException(ErrorConstant.INDEX_CANNOT_OPEN_INDEX_FILE, ex, ex.toString());
      }
    }
    return modifier;
  }  
  
  /**
   * close index immediately or at other thread.
   * @param commitByThread
   * @throws ApplicationException
   */
  static void close(boolean optimize){
    synchronized (threadList) {
      if (threadList.contains(Thread.currentThread())) {
        threadList.remove(Thread.currentThread());
      }
    }
    if (optimize) {
      getIndexOptimizeTask().optimize();
    }else {
      getIndexCommitTask().commit();
    }
  }
  
  /**
   * commit lucene change.
   * @return
   * @throws ApplicationException
   */
  static boolean commit() throws ApplicationException {
    if (threadList.isEmpty() && modifier != null) {
      synchronized (threadList) {
        if (threadList.isEmpty() && modifier != null) {
          try {
            modifier.close();
          }catch (Exception ex) {
            throw new ApplicationException(ErrorConstant.INDEX_CANNOT_CLOSE_INDEX_FILE, ex, ex.toString());
          }finally{
            modifier = null;
          }
        }
      }
      return true;
    }
    return false;
  }
  
  /**
   * Commit and optimize lucene change.
   * @return
   * @throws ApplicationException
   */
  static boolean optimize() throws ApplicationException {
    try {
      if (threadList.isEmpty()) {
        synchronized (threadList) {
          if (threadList.isEmpty()) {
            getIndexModifier(false);
            modifier.optimize();
            modifier.close();
            modifier = null;
          }
        }
        return true;
      }
      return false;
    }catch (Exception ex) {
      throw new ApplicationException(ErrorConstant.INDEX_CANNOT_CLOSE_INDEX_FILE, ex, ex.toString());
    }
  }
  
  private static IndexCommitTask getIndexCommitTask() {
    if (commitTask==null) {
      commitTask = new IndexCommitTask(commitInterval);
      commitTask.start();
    }
    return commitTask;
  }
  
  private static IndexOptimizeTask getIndexOptimizeTask() {
    if (optimizeTask==null) {
      optimizeTask = new IndexOptimizeTask(optimizeInterval);
      optimizeTask.start();
    }
    return optimizeTask;
  }
  
  /**
   * Get index path 'String'.
   * @return
   */
  static String getIndexPath() {
    return indexPath;
  }
  
  /**
   * Get index 'Directory' obj.
   * @return
   */
  static Directory getDirectory() {
    return directory;
  }

  /**
   * Get standard setter Analyzer.
   * @return
   */
  static Analyzer getAnalyzer() {
    return analyzer;
  }

}
